// 解析 HTML
import { Attribute, TextNode, Node } from "../dom";

const EOF = Symbol("EOF"); // End Of File

export interface Token {
  type: string | Symbol;
  tagName?: string;
  content?: string;
  isSelfClosing?: boolean;
  [key: string]: any;
}
let currentToken: Token = null;
let currentAttribute: Attribute = null;
let currentTextNode: TextNode = null;
let stack: Node[] = null; // 存dom结点的栈

/**
 * 将 response 解析出来 html 文本解析成 DOM 树
 * @param response 网络请求得到的响应数据
 * @return 返回 DOM 树
 */
export function parseHTML(response: string) {
  // 每次都需要初始化数据
  stack = [{ type: "document", children: [] }];
  currentToken = null;
  currentAttribute = null;
  currentTextNode = null;

  // 以树数据结构返回dom树的根结点
  let state = data;
  for (const char of response) {
    state = state(char);
  }
  state = state(EOF);
  return stack[0];
}

function emit(token: Token) {
  const top = stack[stack.length - 1];

  if (token.type === "startTag") {
    const element: Node = {
      type: "element",
      children: [],
      attributes: [],
    };

    element.tagName = token.tagName;
    for (const key in token) {
      if (key !== "type" && key !== "tagName" && key !== "isSelfClosing") {
        element.attributes.push({
          name: key,
          value: token[key],
        });
      }
    }

    top.children.push(element);

    // 创建parent指向父级
    element.parent = top;

    if (!token.isSelfClosing) stack.push(element);
    currentTextNode = null;
  } else if (token.type === "endTag") {
    stack.pop();
    currentTextNode = null;
  } else if (token.type === "text") {
    if (currentTextNode === null) {
      currentTextNode = {
        type: "text",
        content: "",
      };
      top.children.push(currentTextNode);
    }
    currentTextNode.content += token.content;
    if (currentTextNode.content === "DOCTYPE html>") top.children.pop();
  }
}

// 状态机
// 捕获各字符
function data(c: string | typeof EOF) {
  if (c === "<") {
    return tagOpen; // 遍历到一个标签起始位置
  }
  if (c === EOF) {
    emit({
      type: EOF,
    });
  } else {
    emit({
      type: "text",
      content: c,
    });
    return data;
  }
  return data;
}

// '<'状态
function tagOpen(c: string) {
  if (c === "/") {
    return endTagOpen; // 遍历到结束标签
  }
  if (c.match(/^[a-zA-Z]$/)) {
    currentToken = {
      type: "startTag",
      tagName: "",
    };
    return tagName(c); // 遍历到起始标签内容
  }
  return data; // <!DOCTYPE html>会在这返回即不挂到DOM上
}

// '<'或'</'后TagName状态
function tagName(c: string) {
  if (c.match(/^[\t\n\f ]$/)) {
    return beforeAttributeName; // 找标签内属性
  }
  if (c === "/") {
    return selfClosingStartTag; // 非成对标签<p/><br/><img/><hr/>
  }
  if (c === ">") {
    emit(currentToken);
    return data; // 起始标签内容结束
  }
  if (c.match(/^[a-zA-Z]$/)) currentToken.tagName += c.toLowerCase();
  return tagName; // 遍历TagName内容
}

// TagName后属性状态
function beforeAttributeName(c: string) {
  if (c === "/" || c === ">") {
    return afterAttributeName(c);
  }
  if (c.match(/^[\t\n\f\s ]$/)) {
    return beforeAttributeName; // 找属性第一个字符
  }
  if (c === "=") {
    throw new Error("This is a unexpected equals sign before attribute name");
  } else {
    currentAttribute = {
      name: "",
      value: "",
    };
    return attributeName(c);
  }
}

// 属性名
function attributeName(c: string) {
  if (c.match(/^[\t\n\f\s ]$/) || c === "/" || c === ">") {
    return afterAttributeName(c);
  }
  if (c === "=") {
    return beforeAttributeValue;
  }
  if (c === '"' || c === "'" || c === "<") {
    throw new Error("This is a unexpected character in attribute name parse error.");
  } else {
    currentAttribute.name += c;
    return attributeName;
  }
}

// 属性结束状态
function afterAttributeName(c: string) {
  if (c.match(/^[\t\n\f\s ]$/)) {
    return afterAttributeName;
  }
  if (c === "/") {
    return selfClosingStartTag;
  }
  if (c === "=") {
    return beforeAttributeValue;
  }
  if (c === ">") {
    currentToken[currentAttribute.name] = currentAttribute.name;
    emit(currentToken);
    return data;
  }
  return beforeAttributeName;
}

// 属性内容
function beforeAttributeValue(c: string) {
  if (c.match(/^[\t\n\f\s ]$/)) {
    return beforeAttributeValue;
  }
  if (c === '"') {
    return doubleQuoteAttributeValue;
  }
  if (c === "'") {
    return singleQuoteAttributeValue;
  }
  return unquoteAttributeValue(c);
}

// 挂载""中的值
function doubleQuoteAttributeValue(c: string) {
  if (c === '"') {
    // 将属性挂到 token 上
    currentToken[currentAttribute.name] = currentAttribute.value;
    return afterQuoteAttributeValue;
  }
  currentAttribute.value += c;
  return doubleQuoteAttributeValue;
}

// 挂载''中的值
function singleQuoteAttributeValue(c: string) {
  if (c === "'") {
    // 将属性挂到 token 上
    currentToken[currentAttribute.name] = currentAttribute.value;
    return afterQuoteAttributeValue;
  }
  currentAttribute.value += c;
  return singleQuoteAttributeValue;
}

// 挂载没有引号的值
function unquoteAttributeValue(c: string) {
  if (c.match(/^[\t\n\f\s ]$/)) {
    currentToken[currentAttribute.name] = currentAttribute.value;
    return afterQuoteAttributeValue(c);
  }
  if (c === "/") {
    currentToken[currentAttribute.name] = currentAttribute.value;
    return selfClosingStartTag;
  }
  if (c === ">") {
    currentToken[currentAttribute.name] = currentAttribute.value;
    emit(currentToken);
    return data;
  }
  currentAttribute.value += c;
  return unquoteAttributeValue;
}

// 挂载完成
function afterQuoteAttributeValue(c: string) {
  if (c.match(/^[\t\n\f\s ]$/)) {
    return beforeAttributeName;
  }
  if (c === "/") {
    return selfClosingStartTag;
  }
  if (c === ">") {
    emit(currentToken);
    return data;
  }
  throw new Error("missing whitespace between attributes parse error.");
}

// '</'后TagName状态
function endTagOpen(c: string) {
  if (c.match(/^[a-zA-Z]$/)) {
    currentToken = {
      type: "endTag",
      tagName: "",
    };
    return tagName(c);
  }
  throw new Error("invalid first character of tag name parse error.");
}

// 非成对标签状态
function selfClosingStartTag(c: string) {
  if (c === ">") {
    currentToken.isSelfClosing = true;
    emit(currentToken);
    return data;
  }
  throw new Error("unexpected solidus in tag parse error.");
}
